Unified session list: backend service + endpoint#139
Merged
Conversation
First increment of the read-only "complete + searchable session list".
- New src/services/unified-session-service.ts: mergeUnifiedSessions() combines
live + persisted (state.json) + lifecycle + ~/.claude transcript history + mux
stats into one list de-duped by sessionId, with precedence
history < lifecycle < persisted < live, a meaningfulness floor that drops bare
lifecycle/mux-only noise, and a stable newest-first sort. Plus
filterAndPaginate() (case-insensitive q over name/firstPrompt/workingDir/
sessionId; total before paging; limit clamped [1,500]). No IO — unit-testable.
- New GET /api/sessions/unified in session-routes.ts: gathers the five sources
from ctx (sessions/store/lifecycle/scanProjectDir/mux, each try/caught), feeds
the pure service, returns { sessions, total } (ApiResponse envelope). testMode
short-circuits to empty.
Tests: unified-session-service.test.ts (12, pure) + unified-sessions-routes.test.ts
(4, app.inject).
createInitialRalphTrackerState() stamps lastActivity: Date.now(). The 'should create fresh instances each time' test deep-equaled two factory results, so two calls straddling a millisecond boundary differed by 1ms and failed intermittently (e.g. PR Ark0N#139 CI: 1782927694581 vs ...580). Exclude the dynamic lastActivity from the equality check and assert it is a number separately, preserving the test's intent (distinct instances with identical initial field values) without the timing race.
…/mode (PR Ark0N#139) - Duplicate rows: transcript-history rows are keyed by the Claude conversation UUID (.jsonl filename stem), which diverges from the Codeman session id for resumed (claudeSessionId = resumeSessionId != id) and /clear-respawned sessions, so one conversation surfaced as both a live row and a history-only row. mergeUnifiedSessions now builds an alias map (claudeSessionId -> Codeman id) from the live + persisted views and resolves history/lifecycle keys through it; the route feeds SessionState.resumeSessionId as the persisted alias. - Inverted precedence: SessionLifecycleLog.query() returns entries NEWEST-first, but the merge loop unconditionally overwrote name/mode so the OLDEST entry in the window won (stale rename/mode). First-seen now wins, mirroring the existing lastActivityAt guard. - Tests: resumed session yields ONE row (service unit + route end-to-end with a real transcript fixture); renamed-then-deleted session surfaces the NEWEST name/mode. All 4 new tests fail against the pre-fix code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
|
Merged — thank you @aakhter! The unified session service is a great foundation (and PR #146's palette plugs straight into it). Review appended one commit: resumed/respawned sessions now dedupe through a claudeSessionId→session-id alias map, and lifecycle name/mode resolution is newest-wins (query() returns newest-first, so the old loop let the oldest entry win). Both covered with tests. 🙏 |
Ark0N
pushed a commit
to aakhter/Codeman
that referenced
this pull request
Jul 12, 2026
…stop Ctrl+K PTY leak, finish shortcut registry (PR Ark0N#146) - Session Manager (COD-121/192): align _loadSessionManagerList() with the merged Ark0N#139 endpoint — map UnifiedSessionItem fields (lastActivityAt epoch-ms → lastModified, optional sizeBytes/firstPrompt/name) to the history-record shape _buildHistoryItem renders; surface non-2xx / error-envelope responses as a visible message instead of a silent "No sessions found"; route clicks by liveness (live row → selectSession, history row → resumeHistorySession by conversation UUID) via a new onActivate option so a live session is never duplicate-resumed - Ctrl+K double-dispatch: gate the palette chord in attachCustomKeyEventHandler (return false on keydown) so xterm never writes 0x0b kill-line into the PTY while the palette opens; gate is registry-aware so a rebound/disabled palette shortcut restores normal terminal Ctrl+K - Shortcut registry (COD-157) finished per maintainer decision: document keydown now dispatches through getShortcutRegistry() + matchesShortcutEvent() (legacy SHORTCUTS table removed), honoring per-shortcut disable and rebinds incl. the palette chord; overrides persist via saveAppSettingsToStorage() (correct device key + cache coherence, was orphaned 'codeman:settings'); Shortcuts tab renders on open via switchSettingsTab hook; capture uses a persistent listener that ignores bare modifier keydowns (combos now capturable) and requires a Ctrl/Cmd/Alt chord; settings rows use delegated listeners instead of inline onclick (JS-string injection sink) and overrides can no longer clobber id/label/action; added the missing row + overlay CSS - matchesShortcutEvent: reject undeclared extra modifiers (Ctrl+Shift+K no longer hijacked from Firefox devtools) while keeping Ctrl/Cmd interchangeable; match physical code OR produced key for layout parity - Registry/dispatch gaps: added restore-terminal-size entry, documented Ctrl+Shift+R again in the help modal (test flipped to assert presence), Ctrl+?/Alt+? now really open the registry-driven shortcut overlay, and Escape closes it - Palette new-session pick routes through selectQuickStartCase() so the searchable combobox, dir display, and lastUsedCase stay in sync - Removed fork cherry-pick debris: dead _onSessionListMaybeChanged(), orphaned .session-row-menu CSS, nonexistent closeMobileHeaderUtilities calls - Tests: functional vm-harness coverage for the unified-list field mapping + error state + liveness routing, palette chord shift/disable/ rebind handling, override persistence round-trip, capture flow, tab render hook, and source guards for the PTY gate + registry dispatch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First increment of a read-only "complete + searchable session list" — the backend half. Frontend (rich list UI, persistent modal, live refresh) and polish (pinning, cross-device ordering, name/prompt retention) follow in stacked PRs.
What this adds
src/services/unified-session-service.ts— pure, IO-free, unit-testable:mergeUnifiedSessions()combines five sources — live sessions + persisted (state.json) + lifecycle log +~/.claudetranscript history + mux stats — into one list de-duped bysessionId, with precedencehistory < lifecycle < persisted < live, a meaningfulness floor that drops bare lifecycle/mux-only noise, and a stable newest-first sort.filterAndPaginate()— case-insensitiveqover name/firstPrompt/workingDir/sessionId;totalcomputed before paging;limitclamped to[1, 500].GET /api/sessions/unifiedinsession-routes.ts— gathers the five sources from ctx (eachtry/caught so one bad source can't fail the request), feeds the pure service, returns{ sessions, total }in the standardApiResponseenvelope. Short-circuits to empty in test mode.Tests
test/services/unified-session-service.test.ts— 12, pure (merge precedence, de-dup, meaningfulness floor, sort, filter, pagination, limit-clamp).test/routes/unified-sessions-routes.test.ts— 4, viaapp.inject(envelope, pagination, q-search, limit-clamp).Verification
tsc --noEmitclean · new tests 16/16 · existingsession-routessuite 53/53 (no regression) · prettier + eslint clean · build clean.Read-only and additive — no existing route or behavior changes.